using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text;
using System.Windows.Forms;
using System.Diagnostics;
using System.IO;

using DevComponents.DotNetBar;
using ZedGraph;

using Pg.BioMedics.RatObserver.Media;
using Pg.BioMedics.RatObserver.ImageProcessing;

using Settings = Pg.BioMedics.RatObserver.Gui.Properties.Settings;

namespace Pg.BioMedics.RatObserver.Gui
{
    public partial class MainForm : Office2007Form
    {
        #region Delegates

        private delegate void VoidDelegate();

        #endregion

        #region Constants

        internal const string MovieFile = ".avi";
        internal const string ResultFile = ".robx";
        internal const int FrameBufferSize = 2;

        #endregion

        #region Fields

        private Video _video;
        private PlayControl _playControl;
        private FrameBuffer _frameBuffer;
        private ImageProcessor _imageProcessor;

        private Packaging.TempVideoReference _tempVideoReference;

        #endregion

        #region Constructors

        public MainForm()
        {
            InitializeComponent();
        }

        #endregion

        #region Event handlers

        private void MainForm_Load(object sender, EventArgs e)
        {
            try
            {
                if (openFileDialog.ShowDialog(this) == DialogResult.OK)
                {
                    if (Path.GetExtension(openFileDialog.FileName).ToLower() == MovieFile)
                        OpenMovieFile(openFileDialog.FileName);
                    else if (Path.GetExtension(openFileDialog.FileName).ToLower() == ResultFile)
                        OpenResultFile(openFileDialog.FileName);
                }
                else
                    Application.Exit();
            }
            catch (Exception ex)
            {
                Utils.Logger.WriteError(ex);
            }
        }

        private void MainForm_FormClosed(object sender, FormClosedEventArgs e)
        {
            Environment.Exit(0);
        }

        private void OpenButton_Click(object sender, EventArgs e)
        {
            try
            {
                if (openFileDialog.ShowDialog(this) == DialogResult.OK)
                {
                    if (Path.GetExtension(openFileDialog.FileName).ToLower() == MovieFile)
                        OpenMovieFile(openFileDialog.FileName);
                    else if (Path.GetExtension(openFileDialog.FileName).ToLower() == ResultFile)
                        OpenResultFile(openFileDialog.FileName);
                }
            }
            catch (Exception ex)
            {
                Utils.Logger.WriteError(ex);
            }
        }

        #region Open result background worker

        private void OpenResultBackgroundWorker_DoWork(object sender, DoWorkEventArgs e)
        {
            BackgroundWorker worker = (BackgroundWorker)sender;
            string packageFile = (string)(((object[])e.Argument)[0]);
            Packaging.VideoInfo videoInfo = (Packaging.VideoInfo)(((object[])e.Argument)[1]);

            worker.ReportProgress(0);            

            // get results and video from package
            Packaging.PackageManager manager = new Packaging.PackageManager(packageFile);
            _tempVideoReference = manager.LoadVideo(videoInfo);
            e.Result = manager.LoadResult(videoInfo);

            worker.ReportProgress(100);
        }

        private void OpenResultBackgroundWorker_ProgressChanged(object sender, ProgressChangedEventArgs e)
        {
            if (e.ProgressPercentage == 0)
            {
                ProgressForm.GetInstance().Show(this);
            }
            else if (e.ProgressPercentage == 100)
            {
                ProgressForm.GetInstance().Hide();
            }
        }

        private void OpenResultBackgroundWorker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
        {
            if (e.Error != null)
            {
                Utils.Logger.WriteError(e.Error);
                ProgressForm.GetInstance().Hide();
            }
            else if (e.Result != null)
            {
                // load video
                _video = new Video(_tempVideoReference.FileName);
                _imageProcessor = new ImageProcessor();

                Packaging.Result result = (Packaging.Result)e.Result;

                // draw rotations graph
                GraphPane graphPane = rotationsZedGraph.GraphPane;

                graphPane.Title.Text = String.Empty;
                graphPane.XAxis.Title.Text = "Frame number";
                graphPane.YAxis.Title.Text = "Level";

                PointPairList points = new PointPairList();
                PointPairList filteredPoints = new PointPairList();

                double[] levels = result.Levels;
                double[] filteredLavels = _imageProcessor.FilterLevels(levels); 
                for (int i = 0; i < levels.Length; i++)
                {
                    points.Add((double)i, levels[i]);
                    filteredPoints.Add((double)i, filteredLavels[i]);
                }

                graphPane.CurveList.Clear();
                graphPane.AddCurve("Levels", points, Color.Red, SymbolType.None);
                graphPane.AddCurve("Filtered Levels", filteredPoints, Color.Blue, SymbolType.None);

                graphPane.AxisChange();
                previewZedGraph.Refresh();

                // assign final values
                rotationsResultPanel.Text = String.Format("Number of rotations - {0}", result.Rotations);

                // set controls state
                mainTabControl.Enabled = true;
                resultsTabItem.Visible = true;
                annotationsTabItem.Visible = true;
            }
        }

        #endregion

        private void SaveButton_Click(object sender, EventArgs e)
        {
            try
            {
                if (saveFileDialog.ShowDialog() == DialogResult.OK)
                {
                    // create video info
                    Packaging.VideoInfo videoInfo = new Packaging.VideoInfo();
                    videoInfo.Id = Guid.NewGuid();
                    videoInfo.Title = titleTextBox.Text;
                    videoInfo.Details = detailsTextBox.Text;
                    videoInfo.Date = experimentDateTimeInput.ValueObject as Nullable<DateTime>;
                    videoInfo.Amplitude = amplitudeDoubleInput.Value;
                    videoInfo.Frequency = frequencyDoubleInput.Value;

                    // create result
                    Packaging.Result result = new Packaging.Result();
                    result.Id = videoInfo.Id;
                    result.Levels = _imageProcessor.MotionLevels;
                    result.Rotations = _imageProcessor.CountRotations(_imageProcessor.FilterLevels(result.Levels), Settings.Default.RotationDiffLevel);
                    result.Direction = "Unknown";

                    // execute work
                    saveBackgroundWorker.RunWorkerAsync(new object[] { videoInfo, result });                    
                }
            }
            catch (Exception ex)
            {
                Utils.Logger.WriteError(ex);
            }
        }

        #region Save background worker

        private void SaveBackgroundWorker_DoWork(object sender, DoWorkEventArgs e)
        {
            BackgroundWorker worker = (BackgroundWorker)sender;

            worker.ReportProgress(0);
            Packaging.VideoInfo videoInfo = (Packaging.VideoInfo)(((object[])e.Argument)[0]);
            Packaging.Result result = (Packaging.Result)(((object[])e.Argument)[1]);

            // save to package
            Packaging.PackageManager manager = new Packaging.PackageManager(saveFileDialog.FileName);
            using (FileStream stream = File.OpenRead(_video.FileName))
            {
                manager.SaveVideo(videoInfo, result, stream);
            }

            worker.ReportProgress(100);
        }

        private void SaveBackgroundWorker_ProgressChanged(object sender, ProgressChangedEventArgs e)
        {
            if (e.ProgressPercentage == 0)
            {
                ProgressForm.GetInstance().Show(this);
            }
            else if (e.ProgressPercentage == 100)
            {
                ProgressForm.GetInstance().Hide();
            }
        }

        private void SaveBackgroundWorker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
        {
            if (e.Error != null)
            {
                Utils.Logger.WriteError(e.Error);
                ProgressForm.GetInstance().Hide();
            }
        }

        #endregion

        private void ConfigurationButton_Click(object sender, EventArgs e)
        {
            new ConfigurationForm().ShowDialog(this);
        }

        private void AboutButton_Click(object sender, EventArgs e)
        {
            new AboutForm().ShowDialog(this);
        }

        private void PlayStopButton_Click(object sender, EventArgs e)
        {
            if (_playControl == null)
            {
                // set up preview 
                _playControl = _video.Play(previewPictureBox);
                _playControl.Play();                

                // set controls state
                playStopButton.Text = "Stop";
                processStopButton.Enabled = false;
                openButton.Enabled = false;
                configurationButton.Enabled = false;
                progressBar.Visible = true;
                progressBar.Value = 0;
                progressTimer.Enabled = true;
            }
            else
            {
                // set up preview
                _playControl.Stop();
                _playControl.Dispose();
                _playControl = null;

                GC.Collect();
                GC.WaitForPendingFinalizers();

                // set controls state
                playStopButton.Text = "Play";
                processStopButton.Enabled = true;
                openButton.Enabled = true;
                configurationButton.Enabled = true;
                progressBar.Visible = false;
                progressTimer.Enabled = false;
            }
        }

        private void ProcessStopButton_Click(object sender, EventArgs e)
        {
            if (_frameBuffer == null)
            {
                // set up processing tools
                _imageProcessor = new ImageProcessor();
                _imageProcessor.NumberOfRegionsX = Settings.Default.RegionsX;
                _imageProcessor.NumberOfRegionsY = Settings.Default.RegionsY;

                _frameBuffer = _video.Extract(FrameBufferSize, BitmapType.RGB24);
                _frameBuffer.OnChanged += new EventHandler(FrameBuffer_OnChanged);
                _frameBuffer.OnStopActive += new EventHandler(FrameBuffer_OnStopActive);
                _frameBuffer.RunActive();

                // set controls state
                processStopButton.Text = "Stop";
                playStopButton.Enabled = false;
                openButton.Enabled = false;
                configurationButton.Enabled = false;
                progressBar.Visible = true;
                progressBar.Value = 0;
                progressTimer.Enabled = true;                
                previewZedGraph.Visible = true;
                statisticsTableLayoutPanel.Visible = true;                

                InitializePreviewGraph();
            }
            else
            {
                _frameBuffer.StopActive();
            }
        }               

        private void ProgressTimer_Tick(object sender, EventArgs e)
        {
            try
            {
                if (_playControl != null)
                {
                    progressBar.Value = _playControl.Progress;
                }
                else if (_frameBuffer != null)
                {
                    progressBar.Value = _frameBuffer.Progress;
                    //totalFramesLabelX.Text = _frameBuffer.TotalFrames.ToString();
                    progressLabelX.Text = _frameBuffer.CurrentFrame.ToString();
                    DrawPreviewGraph();
                }                
            }
            catch (Exception ex)
            {
                progressTimer.Enabled = false;
                Utils.Logger.WriteError(ex);
            }
        }        

        private void FrameBuffer_OnChanged(object sender, EventArgs e)
        {
            try
            {
                if (_frameBuffer.Count > 0)
                    previewPictureBox.Image = _imageProcessor.Process(_frameBuffer.Bitmaps[_frameBuffer.Count - 1]);
                else
                    previewPictureBox.Image = null;                
            }
            catch (Exception ex)
            {
                Utils.Logger.WriteError(ex);

                try
                {
                    _frameBuffer.StopActive();
                    _frameBuffer = null;
                }
                catch { }
            }
        }

        private void FrameBuffer_OnStopActive(object sender, EventArgs e)
        {
            try
            {
                // tear down processing tools
                _frameBuffer = null;

                GC.Collect();
                GC.WaitForPendingFinalizers();                

                // set controls state
                BeginInvoke(new VoidDelegate(delegate 
                {
                    CalculateResults();

                    processStopButton.Text = "Process";
                    playStopButton.Enabled = true;
                    openButton.Enabled = true;
                    saveButton.Enabled = true;
                    configurationButton.Enabled = true;
                    progressBar.Visible = false;
                    progressTimer.Enabled = false;
                    previewZedGraph.Visible = false;
                    statisticsTableLayoutPanel.Visible = false;
                    resultsTabItem.Visible = true;
                    annotationsTabItem.Visible = true;

                    mainTabControl.SelectedTabIndex = 1;
                }));
            }
            catch (Exception ex)
            {
                Utils.Logger.WriteError(ex);
            }
        } 

        #endregion

        #region Support methods

        private void OpenMovieFile(string file)
        {
            // load video
            _video = new Video(file);

            // set controls state
            mainTabControl.Enabled = true;
            resultsTabItem.Visible = false;
            annotationsTabItem.Visible = false;
        }

        private void OpenResultFile(string file)
        {
            try
            {
                // get video infos from package
                Packaging.PackageManager manager = new Packaging.PackageManager(file);
                IList<Packaging.VideoInfo> videoInfos = manager.GetVideoInfos();

                PackageBrowserForm packageBrowserForm = new PackageBrowserForm();
                if (packageBrowserForm.ShowDialog(videoInfos, this) == DialogResult.OK)
                {
                    openResultBackgroundWorker.RunWorkerAsync(new object[] { file, packageBrowserForm.SelectedVideoInfo });

                    // assign annotations
                    titleTextBox.Text = packageBrowserForm.SelectedVideoInfo.Title;
                    detailsTextBox.Text = packageBrowserForm.SelectedVideoInfo.Details;
                    experimentDateTimeInput.ValueObject = packageBrowserForm.SelectedVideoInfo.Date;
                    amplitudeDoubleInput.Value = packageBrowserForm.SelectedVideoInfo.Amplitude;
                    frequencyDoubleInput.Value = packageBrowserForm.SelectedVideoInfo.Frequency;
                }
                else
                    MainForm_Load(this, EventArgs.Empty);
            }
            catch (Exception ex)
            {
                Utils.Logger.WriteError(ex);
            }
        }        

        private void InitializePreviewGraph()
        {
            GraphPane graphPane = previewZedGraph.GraphPane;

            graphPane.Title.Text = String.Empty;
            graphPane.XAxis.Title.Text = "Frames";
            graphPane.YAxis.Title.Text = "Level";

            graphPane.CurveList.Clear();
            graphPane.AxisChange();
        }

        private void DrawPreviewGraph()
        {
            GraphPane graphPane = previewZedGraph.GraphPane;

            PointPairList points = new PointPairList();
            double[] levels = _imageProcessor.MotionLevels;
            for (int i = 0; i < levels.Length; i++)
            {
                points.Add((double)i, levels[i]);
            }

            graphPane.AddCurve(String.Empty, points, Color.Red, SymbolType.None);

            graphPane.AxisChange();
            previewZedGraph.Refresh();
        }

        private void CalculateResults()
        {
            double[] levels = _imageProcessor.MotionLevels;
            double[] filteredLavels = _imageProcessor.FilterLevels(levels,
                                                                   GetMovingAvgFilters(Settings.Default.RotationAvgFilters));
            int rotations = _imageProcessor.CountRotations(filteredLavels, Settings.Default.RotationDiffLevel);


            // draw rotations graph
            GraphPane graphPane = rotationsZedGraph.GraphPane;

            graphPane.Title.Text = String.Empty;
            graphPane.XAxis.Title.Text = "Frame number";
            graphPane.YAxis.Title.Text = "Level";

            PointPairList points = new PointPairList();
            PointPairList filteredPoints = new PointPairList();

            for (int i = 0; i < levels.Length; i++)
            {
                points.Add((double)i, levels[i]);
                filteredPoints.Add((double)i, filteredLavels[i]);
            }

            graphPane.CurveList.Clear();
            graphPane.AddCurve("Levels", points, Color.Red, SymbolType.None);
            graphPane.AddCurve("Filtered Levels", filteredPoints, Color.Blue, SymbolType.None);

            graphPane.AxisChange();
            previewZedGraph.Refresh();


            // draw direction graph
            graphPane = directionZedGraph.GraphPane;

            graphPane.Title.Text = String.Empty;
            graphPane.XAxis.Title.Text = "Frame number";
            graphPane.YAxis.Title.Text = "Level by region";

            double[][] regionLevels = _imageProcessor.RegionMotionLevels;
            int regions = _imageProcessor.NumberOfRegionsX * _imageProcessor.NumberOfRegionsY;
            PointPairList[] regionsPoints = new PointPairList[regions];
            for (int j = 0; j < regions; j++)
                regionsPoints[j] = new PointPairList();

            for (int j = 0; j < regions; j++)
            {
                for (int i = 0; i < levels.Length; i++)
                {
                    regionsPoints[j].Add((double)i, regionLevels[i][j]);
                }
            }
            graphPane.CurveList.Clear();
            for (int j = 0; j < regions; j++)
                graphPane.AddCurve(String.Format("Levels in region {0}", j + 1), regionsPoints[j], GetColor(j), SymbolType.None);

            graphPane.AxisChange();
            previewZedGraph.Refresh();            

            // assign final values
            rotationsResultPanel.Text = String.Format("Number of rotations - {0}", rotations);
            directionResultPanel.Text = String.Format("Direction - CW");
        }

        private int[] GetMovingAvgFilters(StringCollection stringCollection)
        {
            List<int> result = new List<int>();
            for (int i = 0; i < stringCollection.Count; i++)
            {
                int value = 0;
                if (Int32.TryParse(stringCollection[i], out value))
                    result.Add(value);
            }

            return result.ToArray();
        }

        private Color GetColor(int index)
        {
            switch (index)
            {
                case 0:
                    return Color.Red;
                case 1:
                    return Color.Blue;
                case 2:
                    return Color.Green;
                case 3:
                    return Color.Purple;
                default:
                    return Color.Black;
            }
        }

        #endregion                                                                          
    }
}